﻿/*
 * Created by SharpDevelop.
 * User: wwlng1
 * Date: 9/15/2014
 * Time: 10:53 AM
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */
using System;
using System.IO;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Calendar.v3;
using Google.Apis.Calendar.v3.Data;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Google.Apis.Util.Store;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2.Requests;

namespace cogs.Services
{
	/// <summary>
	/// Description of GoogleCalendar.
	/// </summary>
	public class GoogleCalendarService
	{

		private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
		
		private CalendarService service;

		public const string ExtendedPropertyOfficeId="COGS-OfficeId";
		
		public string CurrentCalendarId {get; set;}
		public Calendar CurrentCalendar {get{return GetCalendarById(CurrentCalendarId);}}
		
		private List<Calendar> calendarList = null;

		
		public GoogleCalendarService()
		{
			Initialize();
		}
		
		private void Initialize(){
			log.Debug("Initialize Google Service...");			
		
			UserCredential credential; 

			credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
				GetGoogleClientSecrets(),
				new[] { CalendarService.Scope.Calendar },
				"user",
				CancellationToken.None,
				new FileDataStore("cogs")).Result; 			
		
		 
			var initializer = new BaseClientService.Initializer() {
				HttpClientInitializer = credential,
				ApplicationName = "cogs-2014",
			};
		
			service = new CalendarService(initializer);			
		}
		
		public async Task<UserCredential> AuthorizeAsync(ClientSecrets clientSecrets, IEnumerable<string> scopes, string user, CancellationToken taskCancellationToken, IDataStore dataStore = null)
		{
			GoogleAuthorizationCodeFlow.Initializer initializer = new OfflineGoogleAuthorizationCodeFlow.Initializer {
				ClientSecrets = clientSecrets
			};
			return await AwaitExtensions.ConfigureAwait<UserCredential>(AuthorizeAsyncCore(initializer, scopes, user, taskCancellationToken, dataStore), false);
		}		
		
		private async Task<UserCredential> AuthorizeAsyncCore(AuthorizationCodeFlow.Initializer initializer, IEnumerable<string> scopes, string user, CancellationToken taskCancellationToken, IDataStore dataStore = null)
		{
			initializer.Scopes = scopes;
			initializer.DataStore = (dataStore ?? new FileDataStore(GoogleWebAuthorizationBroker.Folder));
			GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow(initializer);
			return await AwaitExtensions.ConfigureAwait<UserCredential>(new AuthorizationCodeInstalledApp(flow, new LocalServerCodeReceiver()).AuthorizeAsync(user, taskCancellationToken), false);
		}		
		
		
		/// <summary>Retrieves the Client Configuration from the JSON file.</summary>
		/// <returns>Client secrets that can be used for API calls.</returns>
		private ClientSecrets GetGoogleClientSecrets()
		{
			//TODO: If you want to build your own version, please put this json file in your project path
			using (var stream = this.GetType().Assembly.GetManifestResourceStream("cogs.client_secrets.json")){
				return GoogleClientSecrets.Load(stream).Secrets;				
			}
			

		}
		
		
		public List<Calendar> GetCalendarList()
		{
			if(calendarList==null) {
				calendarList = new List<Calendar>();
				var request = service.CalendarList.List();
				request.Alt = CalendarBaseServiceRequest<CalendarList>.AltEnum.Json;
				CalendarList result = request.Execute();
				if(result!=null) {
					log.Debug("Google Calendar List:");
					foreach(var r in result.Items){
						Calendar c= new Calendar(r);
						calendarList.Add(c);
						log.DebugFormat("Calendar: {0}",c);
					}
				}
			}
			return calendarList;
		}

		public Calendar GetCalendarById(string id){
			
			var calendarlist = GetCalendarList();

			foreach(var c in calendarlist){
				if(c.Id==id) {
					return c;
				}
			}
			return null;
		}
		

#region Async Version
		public async Task<List<Calendar>> GetCalendarListAsync()
		{
			if(calendarList==null) {
				calendarList = new List<Calendar>();
				
				var request = service.CalendarList.List();
				request.Alt = CalendarBaseServiceRequest<CalendarList>.AltEnum.Json;
				Task<CalendarList> asyncResult = request.ExecuteAsync();
				CalendarList result = await asyncResult;
				if(result!=null) {
					log.Debug("Google Calendar List:");
					foreach(var r in result.Items){
						Calendar c= new Calendar(r);
						calendarList.Add(c);
						log.DebugFormat("Calendar: {0}",c);
					}
				}
			}
			return calendarList;
		}
		
		public async Task<Calendar> GetCalendarByIdAsync(string id){
			var calendarlist  = await GetCalendarListAsync();
			foreach(var c in calendarlist){
				if(c.Id==id) {
					return c;
				}
			}
			return null;
		}
		

		
#endregion

		
		public List<Appointment> GetCalendarEntriesInRange(int DaysInThePast, int DaysInTheFuture, bool OnlyIncluedItemsSyncFromOutlook)
		{
			List<Appointment> result = new List<Appointment>();
			Events requestResult = null;
			
			
			EventsResource.ListRequest request = service.Events.List(CurrentCalendarId);
			
			request.Alt = CalendarBaseServiceRequest<Events>.AltEnum.Json;
			request.TimeMin = DateTime.Now.AddDays(-DaysInThePast);
			request.TimeMax = DateTime.Now.AddDays(+DaysInTheFuture + 1);
			
			requestResult = request.Execute();
			
			if (requestResult != null) {
				if (requestResult.Items != null) {
					foreach (Event e in requestResult.Items) {
						
						if(OnlyIncluedItemsSyncFromOutlook) {
							if(	e.ExtendedProperties!=null &&
							   e.ExtendedProperties.Private[GoogleCalendarService.ExtendedPropertyOfficeId]!=null ) {
								result.Add(new Appointment(e));
							}
						}
						else {
							result.Add(new Appointment(e));
						}
					}
				}
			}
			return result;
		}

		public void DeleteCalendarEntry(Appointment a)
		{
			var request = service.Events.Delete(CurrentCalendarId, a.GoogleId);
			request.Alt =  CalendarBaseServiceRequest<string>.AltEnum.Json;
			var result = request.Execute();
			
		}
		
		public void InsertCalendarEntry(Appointment a)
		{
			var request = service.Events.Insert(a.ToGoogleCalendarEvent(), CurrentCalendarId);
			request.Alt =  CalendarBaseServiceRequest<Event>.AltEnum.Json;
			var result = request.Execute();
		}

		public async Task DeleteCalendarEntryAsync(Appointment a)
		{
			var request = service.Events.Delete(CurrentCalendarId, a.GoogleId);
			request.Alt =  CalendarBaseServiceRequest<string>.AltEnum.Json;
			var result = await request.ExecuteAsync();
			
		}
		
		public async Task<Appointment> InsertCalendarEntryAsync(Appointment a)
		{
			var request = service.Events.Insert(a.ToGoogleCalendarEvent(), CurrentCalendarId);
			request.Alt =  CalendarBaseServiceRequest<Event>.AltEnum.Json;			
			var result = await request.ExecuteAsync();
			a.GoogleId = result.Id;
			a.GoogleETag =result.ETag;
			return a;
		}		
		
	}
	
	internal class OfflineGoogleAuthorizationCodeFlow : GoogleAuthorizationCodeFlow
	{
		public OfflineGoogleAuthorizationCodeFlow(AuthorizationCodeFlow.Initializer initializer)
			: base(initializer)
		{
		}

		public override AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(string redirectUri)
		{
			return new GoogleAuthorizationCodeRequestUrl(new Uri(AuthorizationServerUrl)) {
				ClientId = ClientSecrets.ClientId,
				Scope = string.Join(" ", Scopes),
				RedirectUri = redirectUri,
				AccessType = "offline"
			};
		}
	};
	
}
